<?php

// @formatter:off
/**
 * @file WCComic.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-06-03 03:40:32 +0000 (Tue, 03 Jun 2014) $
 *
 * $Id: WCComic.php 112 2014-06-03 03:40:32Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/representations/WCComic.php $
 */
// @formatter:on

/**
 * @class WCComic This class represents entries saved in the table "%comics".
 */
class WCComic extends WCRepresentationGroup implements WCPreviews, WCSearchIndexable, WCHardRemove, WCTrashed, WCFlowItem {
	//
	// Protected properties.
	/**
	 * @var string[] This variable holds the list of columns that cannot be
	 * directly modified by the pseudo-method __set().
	 * In this case cmc_id, cmc_group, cmc_directory and cmc_name.
	 */
	protected $_PropertiesReadOnly = array(
		"id",
		"group",
		"directory",
		"name"
	);
	/**
	 * @var string[] This variable holds the list of columns that must be
	 * treated as booleans and then send to the database as 'Y' and 'N' by
	 * the pseudo-method __set().
	 * In this case cmc_checked, cmc_generated, cmc_indexed, cmc_rated and
	 * cmc_lost.
	 */
	protected $_PropertiesBoolean = array(
		"checked",
		"generated",
		"indexed",
		"rated",
		"lost"
	);
	/**
	 * @var string This is the prefix of every column on the table
	 * '%comics'.
	 */
	protected $_PropertiesPrefix = "cmc_";
	/**
	 * @var string This is the name of the represented table. In this case,
	 * '%comics'.
	 */
	protected $_PropertiesTable = "comics";
	/**
	 * @var WCCollection This is a pointer to the collection that contains
	 * this comic.
	 */
	protected $_collection = null;
	/**
	 * @var WCCollection This is a pointer to the directory where this comic
	 * is located.
	 */
	protected $_directory = null;
	/**
	 * @var boolean This flag indicates that the full-path and URI to the
	 * expanded directory were loaded or not.
	 */
	protected $_expandedLoaded = false;
	/**
	 * @var string This is the full-path where the comic is temporally
	 * expanded in order to be shown in the web page. For example:
	 * 	- /var/www/wcomix/cache/expanded/1/0c9610e6361e88791b85c132ddecdac4
	 */
	protected $_expandedPath = false;
	/**
	 * @var string This is similar to $_expandedPath but is only the URI to the
	 * expanded directory.
	 * It is a full path relative to the server. For example:
	 * 	- /wcomix/cache/expanded/1/0c9610e6361e88791b85c132ddecdac4
	 */
	protected $_expandedUri = false;
	protected $_hardTrashed = false;
	/**
	 * @var WCComicImage[] This is the list of images contained by this comic.
	 */
	protected $_images = array();
	/**
	 * @var boolean This flag indicates that full paths and URIs for previews
	 * were loaded or not.
	 */
	protected $_pathsLoaded = false;
	/**
	 * @var string This is the full path to the directory where all comic
	 * thumbnails will be stored. For example:
	 * 	- /var/www/wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc
	 */
	protected $_previewDirPath = false;
	/**
	 * @var string This is similar to '$_previewDirPath', but it is the URI.
	 * It is a full path relative to the server. For example:
	 * 	- /wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc
	 */
	protected $_previewDirUri = false;
	/**
	 * @var string This is the image full path of the comic's thumbnail. For
	 * example:
	 * 	- /var/www/wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc.png
	 */
	protected $_previewPath = false;
	/**
	 * @var string This is similar to '$_previewPath', but it is the URI.
	 * It is a full path relative to the server. For example:
	 * 	- /wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc.png
	 */
	protected $_previewUri = false;
	protected $_path = false;
	/**
	 * @var string This is the root path of the group that contains this
	 * comic.
	 */
	protected $_root = false;
	protected $_trashed = false;
	protected $_trashedFlagsLoaded = false;
	//
	// Magic methods.
	/**
	 * Class basic constructor
	 */
	public function __construct() {
		//
		// Calling the parent class constructor.
		parent::__construct();
	}
	/**
	 * Class basic destructor.
	 */
	public function __destruct() {
		//
		// This class has nothing to clean before deleting.
	}
	public function __toString() {
		return $this->ok() ? "{$this->name()} ({$this->id()}[g:{$this->group},d:{$this->directory}])" : "broken item";
	}
	//
	// Public methods.
	/**
	 * This method allows to load and retrieve the collection where this comic
	 * is held.
	 *
	 * @return WCCollection Returns a collection pointer. When the comic has
	 * no collection or if there where a problem retrieving it, it returns
	 * a null pointer.
	 */
	public function collection() {
		//
		// Checking if the comic belongs to a collection.
		if(!$this->_collection) {
			//
			// Loading the collections holder.
			global $wcCollectionsHolder;
			//
			// Asking for the collection to the holder.
			$this->_collection = $wcCollectionsHolder->item($this->collection);
			//
			// If the collection is found and it has no issues, it can
			// be returned, otherwise, returns null.
			if(!$this->_collection || !$this->_collection->ok()) {
				$this->_collection = null;
			}
		}
		//
		// Returning the found collection.
		return $this->_collection;
	}
	/**
	 * This method allows to get a pointer to the directory where the comic is
	 * located.
	 *
	 * @return WCDirectory Returns a directory pointer.
	 */
	public function directory() {
		//
		// Checking id the pointer was not already loaded.
		if(!$this->_directory) {
			//
			// Asking for the directory to the holder.
			$this->_directory = WCDirectoriesHolder::I()->item($this->directory);
			//
			// If the directory is found and it has no issues it can
			// be returned, otherwise, the pointer remains null.
			if(!$this->_directory || !$this->_directory->ok()) {
				$this->_directory = null;
			}
		}
		// Returning the found pointer.
		return $this->_directory;
	}
	/**
	 * This method expand all of this comic contents into the expansion
	 * directory.
	 *
	 * @return boolean Returns true when the comic was successfully expanded.
	 */
	public function expand() {
		//
		// Checking that this comic has no issues, that it is not lost and
		// that it's not already expanded.
		if($this->ok() && !$this->isExpanded() && !$this->lost) {
			//
			// Loading global default settings.
			// @{
			global $wcDefaults;
			// @}
			//
			// Checking if the expansion directory must be physically
			// created.
			if(!is_dir($this->expandedPath())) {
				//
				// Physically creating the directory.
				mkdir($this->expandedPath(), $wcDefaults['directory-permissions'], true);
			}
			//
			// Building the full path of the comic.
			$comicPath = $this->path();
			//
			// Checking that it is possible to read the file.
			if(is_readable($comicPath)) {
				//
				// Loading global pointers.
				global $wcCompressors;
				//
				//
				$comicPathInfo = pathinfo($comicPath);
				//
				// Starting a variable to create the extraction
				// command.
				$cmd = "";
				//
				// Searching for a command to expand this comic.
				$found = false;
				foreach($wcCompressors as $type => $compressor) {
					if(in_array($comicPathInfo["extension"], $compressor["extensions"])) {
						$found = $type;
						break;
					}
				}
				//
				// Checking if a command was found.
				if($found) {
					$cmdBin = getBinaryFullPath($wcCompressors[$found]["binary"]);
					//
					// Checking if the command to run has a
					// valid binary file.
					if($cmdBin != "") {
						//
						// Building expansion command
						// based on the extension of the
						// comic.
						$cmd = $wcCompressors[$found]["cmd-skeleton"];
						$cmd = str_replace("{COMMAND}", $cmdBin, $cmd);
						$cmd = str_replace("{COMIC}", $comicPath, $cmd);
						$cmd = str_replace("{DIRECTORY}", $this->expandedPath(), $cmd);
						//
						// Running the command in the
						// system. All generated output
						// will be dropped.
						/** @todo on the installation it must be checked that exec is allowed before
						 * installing. */
						ob_start();
						exec($cmd, $aux, $return_var);
						echo "Returned: $return_var\n";
						echo implode("\n", $aux);
						$cmdOutput = ob_get_clean();
						if($wcDefaults["debug"]) {
							if(!defined("__SHELL__")) {
								echo "<!--\n";
							}

							echo "Comand: {$cmd}\n";
							echo "results:\n{$cmdOutput}\n";

							if(!defined("__SHELL__")) {
								echo "-->\n";
							}
						}
					} else {
						//
						// Removing physical directory to avoid
						// further errors.
						rmdir($this->expandedPath());
						//
						// If no command was generated an
						// exception is raised.
						$pathVar = DIRECTORY_SEPARATOR == "/" ? "\$PATH" : "%PATH%";
						trigger_error(__CLASS__.": Command '{$wcCompressors[$found]["binary"]}' couldn't be transformed into a valid full path (check your '{$pathVar}')", E_USER_ERROR);
					}
				} else {
					//
					// Removing physical directory to avoid
					// further errors.
					rmdir($this->expandedPath());
					//
					// If no command was generated an
					// exception is raised.
					trigger_error(__CLASS__.": Unable to expand comic '{$this}'", E_USER_ERROR);
				}
			}
		}
		//
		// The result of this method is the same one of 'isExpanded()'.
		return $this->isExpanded();
	}
	/**
	 * This method allows to get the system full path where all elements
	 * extracted from the comic will be temporarily stored.
	 *
	 * @return string Returns a system full path.
	 */
	public function expandedPath() {
		//
		// Loading paths.
		$this->loadExpanded();
		//
		// Returning the path.
		return $this->_expandedPath;
	}
	/**
	 * This method allows to get the URI where all elements extracted from the
	 * comic will be temporarily stored.
	 *
	 * @return string Returns a URI relative to the server.
	 */
	public function expandedUri() {
		//
		// Loading paths.
		$this->loadExpanded();
		//
		// Returning the URI.
		return $this->_expandedUri;
	}
	public function flowItemType() {
		return WCFlow::ItemTypeComic;
	}
	/**
	 * This method generates the comic preview.
	 *
	 * @return string returns the full path of the comic preview image.
	 */
	public function generatePreviews() {
		//
		// Checking that there no issues with this comic and that it's
		// preview is not yet generated.
		if($this->ok() && !$this->hasValidPreviews()) {
			//
			// Autoenforcing some checks.
			$this->images();
			//
			// Invalidating view cache.
			WCViewManager::I()->invalidate(WCViewManager::KeyComic, $this->id());

			if(WCPreviewsManager::I()->createComicPreviews($this)) {
				$this->setValidPreviews(true);
			}
			//
			// Setting to false the flag that controls preview paths.
			$this->_pathsLoaded = false;
			//
			// Loading preview paths.
			$this->loadPaths();
		}
		//
		// Returning the generated path.
		return $this->hasValidPreviews();
	}
	public function hardRemove() {
		$out = false;

		$filename = WCSanitizer::SystemPath("{$this->directory()->fullPath()}/{$this->name}");
		if(@unlink($filename)) {
			$out = $this->remove();
		}

		return $out;
	}
	/**
	 * This method allows to know if the comic has a preview.
	 *
	 * @return boolean Returns true when the comic preview exists.
	 */
	public function hasPreview() {
		//
		// Loading preview's paths.
		$this->loadPaths();
		//
		// Returns true when the preview image exists.
		return $this->_previewPath != false;
	}
	/**
	 * This method allows to know if the object has a list of preview images.
	 *
	 * @return boolean Returns true when the object has a list of preview
	 * images.
	 */
	public function hasPreviews() {
		return false;
	}
	public function hasValidPreviews() {
		return $this->generated;
	}
	/**
	 * @todo doc
	 * 
	 * @return WCComicImage[]
	 */
	public function images() {
		//
		// Loading the list of images.
		$this->loadImages();
		//
		// Returning the list.
		return $this->_images;
	}
	/**
	 * This method allows to know whether an item is indexed or not. Also it
	 * allows to change such status.
	 *
	 * @param boolean $value New status to be set. When absent, this method
	 * works as a getter.
	 * @return boolean Returns true when the object is indexed.
	 */
	public function indexed($value = null) {
		if(!is_null($value)) {
			$this->indexed = $value;
		}

		return $this->indexed;
	}
	/**
	 * This method allows to if the comic is already expanded.
	 *
	 * @return boolean Returns true when the expansion directory exists.
	 */
	public function isExpanded() {
		return is_dir($this->expandedPath());
	}
	public function isHardTrashed() {
		$this->loadTrashedFlags();
		return $this->_hardTrashed;
	}
	public function isTrashed() {
		$this->loadTrashedFlags();
		return $this->_trashed;
	}
	/**
	 * This method load the a comic based on its information saved in the
	 * table '%comics'.
	 *
	 * @param int $id Identificator for the comic.
	 * @param WCGroup $group Group that contains the comic to load.
	 * @return boolean Retruns true when there were no problems loading the
	 * comic.
	 */
	public function load($id) {
		$this->_ok = false;
		//
		// Because comic object loading may require may require more than
		// one try to get all the information, this flag indicates if
		// loadings should keep going on.
		$keepLoading = true;
		while($keepLoading) {
			//
			// By default, it is considered that only one try is
			// enough.
			$keepLoading = false;
			//
			// Calling the 'load()' method of parent class
			// WCGTableRepresentation
			parent::load($id);
			//
			// Checking if the database entry loaded without any
			// problem and it represents valid comic
			if($this->ok()) {
				//
				// If there are neither a thumbnail's URI nor a
				// expanded URI, these must be generated.
				if($this->id !== false && (!$this->thumbs || !$this->expanded)) {
					//
					// Creating a hash code based on
					// properties of the current comic.
					$code = md5("{$this->group}|{$this->directory()->name}|{$this->name}");
					//
					// Setting a URI for expansion directory.
					$this->expanded = "/{$code}";
					//
					// Setting a URI for thumbnails directory.
					$this->thumbs = "/{$code[0]}/{$code[1]}{$code[2]}{$code[3]}/{$code}";
					//
					// At this point, many properties were
					// modified on the database and to avoid
					// inconsistencies, loading must be
					// retried.
					$keepLoading = true;
				}
				//
				// Setting the comic root directory with the group
				// root path.
				$this->_root = $this->group()->root();
				//
				// Checking some issues:
				//	- A not null id means the parent class
				//	  loaded the comic without problems.
				//	- A value in the directory property means
				//	  the comic is not located in the limbus
				//	  O_o
				//	  Zero isn't a valid id either.
				//	- If the comic is flagged as lost, it is
				//	  no good for normal usage.
				$this->_ok = ($this->id !== false && $this->directory && !$this->lost);
			}
		}
		//
		// Return the same result than method 'ok()'.
		return $this->ok();
	}
	/**
	 * This method allows to get the name of the comic. It is different to
	 * directly use the property 'name' through the pseudo-method __get()
	 * because, in this case, if the comic is scheduled to be rename, it
	 * returns the name it will have.
	 * This provides a transparent use of the comic name beyond any renaming.
	 *
	 * @return string Returns the name of the represented comic.
	 */
	public function name() {
		//
		// Choosing and returning the right name.
		return $this->next_name ? $this->next_name : $this->name;
	}
	public function path() {
		//
		// Loading paths.
		$this->loadPaths();
		//
		// Returning path.
		return $this->_path;
	}
	/**
	 * This method is similar to 'previewPath()', but for the URI.
	 *
	 * @return string Returns a URI.
	 */
	public function preview() {
		global $wcDefaults;
		//
		// Loading preview's paths.
		$this->loadPaths();
		//
		// Returning the URI.
		return $this->hasPreview() ? $this->_previewUri : $wcDefaults["preview"];
	}
	/**
	 * This method allows to get the full path to the directory where all
	 * comic thumbnails will be stored.
	 *
	 * @return string Returns a full path.
	 */
	public function previewDirPath() {
		//
		// Loading preview's paths.
		$this->loadPaths();
		//
		// Returning the full path.
		return $this->_previewDirPath;
	}
	/**
	 * This method is similar to 'previewDirPath()', but for the URI.
	 *
	 * @return string Returns a URI.
	 */
	public function previewDirUri() {
		//
		// Loading preview's paths.
		$this->loadPaths();
		//
		// Returning the URI.
		return $this->_previewDirUri;
	}
	/**
	 * This method allows to get the image full path of the comic's thumbnail.
	 *
	 * @return string Returns a full path.
	 */
	public function previewPath() {
		//
		// Loading preview's paths.
		$this->loadPaths();
		//
		// Returning the full path.
		return $this->_previewPath;
	}
	public function previews() {
		return array($this->preview());
	}
	public function reload() {
		$this->_collection = null;
		$this->_directory = null;
		$this->_images = array();
		$this->_root = false;
		$this->_pathsLoaded = false;
		$this->_previewDirPath = false;
		$this->_previewDirUri = false;
		$this->_previewPath = false;
		$this->_previewUri = false;
		$this->_expandedLoaded = false;
		$this->_expandedPath = false;
		$this->_expandedUri = false;
		$this->_path = false;

		return parent::reload();
	}
	/**
	 * This method allows to get the group root path where the comic is
	 * physically stored.
	 *
	 * @return string Returns a full path to the group's directory.
	 */
	public function root() {
		return $this->_root;
	}
	/**
	 * This method allows to physically move a comic from a directory to
	 * another.
	 *
	 * Even though this operation can be done manually by any system user,
	 * this method will also keep all associations made with the comic.
	 * Otherwise, a manual operation will cause two things:
	 * 	- the new path of the comic will be taken as a new comic.
	 * 	- and the current comic will become lost.
	 * And all this thing will occur on the next cron execution. Meanwhile
	 * many undesired behaviors will be seen.
	 *
	 * @param WCDirectory $directory Directory where the comic will be moved
	 * to.
	 * @return boolean Returns true where the operation was performed without
	 * any issue.
	 */
	public function setDirectory(WCDirectory &$directory) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// This is the default value to be returned.
		$out = false;
		//
		$this->reload();
		//
		// Checking that:
		//	- Read Only Mode is not activated.
		//	- there are no issues with the current comic
		//	- there are no issues with the destination directory.
		//	- the comic and the destination directory belong to the
		//	  same group.
		//	- The comic is not already in the destination directory.
		if(!$wcDefaults["read-only"] && $this->ok() && $directory->ok() && $this->group == $directory->group && $directory->id() != $this->directory) {
			//
			// Setting that it is ok to continue with the operation.
			$ok = true;
			//
			// Creating an auxiliary pointer to the current comic
			// directory.
			$curDirectory = $this->directory();
			//
			// Building all needed paths.
			// @{
			$dirPathFrom = $curDirectory->path();
			$dirPathTo = $directory->path();
			$comicPathFrom = WCSanitizer::SystemPath("{$dirPathFrom}/{$this->name}");
			$comicPathTo = WCSanitizer::SystemPath("{$dirPathTo}/{$this->name}");
			// @}
			//
			// Checking if it's possible to write in the current
			// comic's directory.
			if($ok && !is_writable($dirPathFrom)) {
				//
				// If it's not possible, an exception is raised.
				WCErrors::I()->error(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "Writting access denied on directory '{$dirPathFrom}'");
				$ok = false;
			}
			//
			// Checking if it's possible to write in the destination
			// directory.
			if($ok && !is_writable($dirPathTo)) {
				//
				// If it's not possible, an exception is raised.
				WCErrors::I()->error(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "Writting access denied on directory '{$dirPathTo}'");
				$ok = false;
			}
			//
			// Checking if current comic file exits.
			if($ok && !file_exists($comicPathFrom)) {
				$this->lost = true;
				//
				// If not, an exception is raised.
				WCErrors::I()->error(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "Origin comic '{$comicPathTo}' doesn't exist");
				$ok = false;
			}
			//
			// Checking if future path exists.
			if($ok && file_exists($comicPathTo)) {
				//
				// If not, an exception is raised.
				WCErrors::I()->error(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "Destination comic '{$comicPathTo}' already exists");
				$ok = false;
			}
			//
			// If everything is ok, the comic is moved.
			if($ok) {
				//
				// Moving the comic.
				$moved = rename($comicPathFrom, $comicPathTo);
				//
				// Checking that the new path exists and the old
				// one doesn't.
				if(!$moved || is_file($comicPathFrom) || !is_file($comicPathTo)) {
					//
					// If any of this didn't happened, an
					// exception is raised.
					WCErrors::I()->error(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "Unable to move comic {$this}. From: '{$comicPathFrom}'. To: '{$comicPathTo}'.");
					$ok = false;
				}
			}
			//
			// If everything is ok, it means that the comic was
			// successfully moved
			if($ok) {
				//
				// Cleaning the expansion path associated with the
				// old path.
				@recursive_unlink($this->expandedPath());
				//
				// Cleaning thumbnails' paths associated with the
				// old path.
				@recursive_unlink($this->previewDirPath());
				//
				// Cleaning the comic thumbnail associated with
				// the old path.
				@unlink($this->previewPath());
				//
				// Since comic was moved destination directory's
				// previews are no longer valid.
				$directory->setValidPreviews(false);
				//
				// Since comic was moved destination directory's
				// counts should be increased in one.
				$directory->count++;
				//
				// New directory object should reload to avoid
				// further problems.
				$directory->reload();
				//
				// Since comic was moved origin directory's
				// previews are no longer valid.
				$curDirectory->setValidPreviews(false);
				//
				// Since comic was moved origin directory's
				// counts should be decreased in one.
				$curDirectory->count--;
				//
				// Old directory object should reload to avoid
				// further problems.
				$curDirectory->reload();
				//
				// If there'ss one, collection's previews are also
				// no longer valid.
				if($this->collection()) {
					$this->collection()->setValidPreviews(false);
				}
				//
				// Preparing a statement to update the comic.
				// @{
				$stmt = false;
				$stmtID = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtID)) {
					$stmt = $this->_db->get($stmtID);
				} else {
					$query = "update  {$this->_dbprefix}comics\n";
					// Current directory should be the new one.
					$query .= "set     cmc_directory = :directory_s,\n";
					// Thumbnails URI changed.
					$query .= "        cmc_thumbs    = '',\n";
					// Expansion URI changed.
					$query .= "        cmc_expanded  = '',\n";
					// Since some URIs changed, it should be
					// regenerated.
					$query .= "        cmc_generated = 'N'\n";
					$query .= "where   cmc_group     = :group\n";
					$query .= " and    cmc_id        = :comic\n";

					$stmt = $this->_db->prepare($stmtID, $query);
				}
				// @}
				//
				// Sending updates to the database.
				$stmt->execute(array(
					":directory_s" => $directory->id,
					":group" => $this->group()->id,
					":comic" => $this->id
				));
				$this->indexed = false;
				//
				// Many properties of the comic had changed,
				// therefore, to avoid problems, it is reloaded.
				$out = $this->reload();
			}
		}
		//
		// Returning the operation results.
		return $out;
	}
	/**
	 * This method performs the scheduled renaming operation.
	 *
	 * @return boolean Returns true when the operation was performed without
	 * issues. If the comic was not scheduled for renaming, it returns false.
	 */
	public function setNextName() {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$ok = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// This is the default value to be returned.
			$ok = true;
			//
			// Generating full paths for origin and destination.
			$oldPath = WCSanitizer::SystemPath("{$this->directory()->fullPath()}/{$this->name}");
			$newPath = WCSanitizer::SystemPath("{$this->directory()->fullPath()}/{$this->next_name}");
			//
			// Making some preliminary checks:
			//	- If the comic wasn't scheduled, it is an error.
			//	- If the current full path isn't a file, it is an
			//	  error.
			//	- If the destination full path is an existing
			//	  file, it is an error.
			$ok = $this->next_name != "" && is_file($oldPath) && !file_exists($newPath);
			//
			// If everything is ok, the file is renamed.
			if($ok) {
				//
				// Renaming the file.
				rename($oldPath, $newPath);
				//
				// Checking that the new file exists and the old
				// one doesn't.
				$ok = !is_file($oldPath) && is_file($newPath);
			}
			//
			// If the comic was successfully renamed
			if($ok) {
				//
				// Cleaning the expansion path associated with the
				// old name.
				@recursive_unlink($this->expandedPath());
				//
				// Cleaning thumbnails' paths associated with the
				// old name.
				@recursive_unlink($this->previewDirPath());
				//
				// Cleaning the comic thumbnail associated with
				// the old name.
				@unlink($this->previewPath());
				//
				// Since comic previews will be regenerated, its
				// directory's previews are no longer valid.
				$this->directory()->setValidPreviews(false);
				//
				// If there'ss one, collection's previews are also
				// no longer valid.
				if($this->collection()) {
					$this->collection()->setValidPreviews(false);
				}
				//
				// Preparing a statement to update the comic.
				// @{
				$stmt = false;
				$stmtID = __CLASS__."::".__FUNCTION__."[comics]";
				if($this->_db->has($stmtID)) {
					$stmt = $this->_db->get($stmtID);
				} else {
					$query = "update  {$this->_dbprefix}comics\n";
					// Current name should be the new one.
					$query .= "set     cmc_name      = :name_s,\n";
					// Comic is no longer scheduled for
					// renaming.
					$query .= "        cmc_next_name = '',\n";
					// Thumbnails URI changed.
					$query .= "        cmc_thumbs    = '',\n";
					// Expansion URI changed.
					$query .= "        cmc_expanded  = '',\n";
					// Since some URIs changed, it should be
					// regenerated.
					$query .= "        cmc_generated = 'N',\n";
					// It's name changed, so, it must be
					// re-indexed by the search engine.
					$query .= "        cmc_indexed   = 'N'\n";
					$query .= "where   cmc_group = :group\n";
					$query .= " and    cmc_id    = :id\n";

					$stmt = $this->_db->prepare($stmtID, $query);
				}
				// @}
				//
				// Sending updates to the database.
				$stmt->execute(array(
					":name_s" => $this->next_name,
					":group" => $this->group()->id(),
					":id" => $this->id
				));

				$this->indexed = false;
				//
				// Many properties of the comic had changed,
				// therefore, to avoid problems, it is reloaded.
				$ok = $this->load($this->id, $this->group());
			}
			if(!$ok && $oldPath == $newPath) {
				$this->next_name = "";
			}
		}
		//
		// Returning the process results.
		return $ok;
	}
	public function setValidPreviews($valid = true) {
		$this->generated = $valid;
	}
	public function shrink() {
		//
		// Checking that this comic has no issues, that it is not lost and
		// that it's already expanded.
		if($this->ok() && $this->isExpanded() && !$this->lost) {
			if(is_dir($this->expandedPath())) {
				//
				// Physically removing the directory.
				recursive_unlink($this->expandedPath());
			}
		}
		//
		// The result of this method is the same one of 'isExpanded()'.
		return $this->isExpanded();
	}
	/**
	 * This method allows to get a list of phrases that must be associated with an
	 * item.
	 *
	 * @return string[] Returns a list of phrases.
	 */
	public function words() {
		$words = WCSearch::StringToPatterns($this->name());
		foreach($this->directory()->words() as $word) {
			$words[] = $word;
		}
		if($this->collection > 0) {
			foreach($this->collection()->words() as $word) {
				$words[] = $word;
			}
		}
		foreach(WCTagsHolder::I()->tagsOfComic($this) as $tag) {
			foreach($tag->words() as $word) {
				$words[] = $word;
			}
		}
		return $words;
	}
	//
	// Protected methods.
	protected function generateSubEntries() {
		$items = array();
		//
		// Loading the global patterns settings.
		global $wcPatterns;
		//
		// This operation requires the comic to be expanded.
		$this->expand();
		//
		// Seeking all images of this comic.
		findPaths($this->expandedPath(), "", $items, $wcPatterns["image-paths"], true);

		$holder = WCComicImagesHolder::I();
		$position = 0;
		foreach($items as $item) {
			$image = $holder->getEntry($this, $position, $item->lpath);
			if(!$image) {
				$image = $holder->addEntry($this, $position, $item->lpath);
			}
			$position++;
		}
	}
	/**
	 * This method auto-sets the paths of the physical expanded directory and
	 * its URI.
	 */
	protected function loadExpanded() {
		//
		// Checking that there are no issues with this object and that
		// this operation was not run before.
		if($this->ok() && !$this->_expandedLoaded) {
			//
			// Loading global path settings.
			global $wcPaths;
			//
			// Setting full path and URI.
			$this->_expandedPath = WCSanitizer::SystemPath("{$wcPaths["cache-directory"]}/{$wcPaths["cache-expand-dirname"]}/{$this->group()->id}{$this->expanded}");
			$this->_expandedUri = WCSanitizer::SystemPath("{$wcPaths["cache-directory-uri"]}/{$wcPaths["cache-expand-dirname"]}/{$this->group()->id}{$this->expanded}");
			//
			// Setting the flag to "already done".
			$this->_expandedLoaded = true;
		}
	}
	/**
	 * This method loads the list of images contained by the comic.
	 */
	protected function loadImages() {
		$this->_images = WCComicImagesHolder::I()->itemsByComic($this);
		if(!count($this->_images)) {
			$this->generateSubEntries();
			$this->_images = WCComicImagesHolder::I()->itemsByComic($this);
		}
	}
	/**
	 * This method auto-sets the paths of the physical previews and their
	 * URIs.
	 */
	protected function loadPaths() {
		//
		// Checking that there are no issues with this object and that
		// this operation was not run before.
		if($this->ok() && !$this->_pathsLoaded) {
			//
			// Loading global path settings.
			global $wcPaths;

			$previews = WCPreviewsManager::I()->getPreviews($this->group(), WCPreviewsManager::TypeComic, $this->id);
			//
			// Setting full path and URI for the comic's thumbnails.
			$this->_previewDirPath = WCSanitizer::SystemPath("{$wcPaths["cache-directory"]}/{$wcPaths["cache-thumbs-dirname"]}/{$this->group()->id}{$this->thumbs}");
			$this->_previewDirUri = WCSanitizer::WComixPath("{$wcPaths["cache-directory-uri"]}/{$wcPaths["cache-thumbs-dirname"]}/{$this->group()->id}{$this->thumbs}");

			if(count($previews) > 0 && isset($previews[0])) {
				$this->_previewPath = WCSanitizer::SystemPath("{$wcPaths["cache-directory"]}/{$wcPaths["cache-thumbs-dirname"]}/{$previews[0]}");
				$this->_previewUri = WCSanitizer::WComixPath("{$wcPaths["cache-directory-uri"]}/{$wcPaths["cache-thumbs-dirname"]}/{$previews[0]}");
			} else {
				$this->_previewPath = false;
				$this->_previewUri = false;
				$this->setValidPreviews(false);
			}
			//
			// Setting full path and URI.
			$this->_path = WCSanitizer::SystemPath("{$this->directory()->fullPath()}/{$this->name}");
			//
			// Setting the flag to "already done".
			$this->_pathsLoaded = true;
		}
	}
	protected function loadTrashedFlags() {
		if($this->ok() && !$this->_trashedFlagsLoaded) {
			$trashDir = $this->group()->trash();
			$this->_hardTrashed = $trashDir && $this->directory == $trashDir->id;

			$trashTag = WCActiveTagsHolder::I()->get(WC_ACTIVE_TAG_TRASH, $this->group())->tag();
			$this->_trashed = $trashTag && $trashTag->ok() && $trashTag->hasComic($this);

			$this->_trashedFlagsLoaded = true;
		}
	}
	/**
	 * This is an internal method called by the pseudo-method __set()
	 *
	 * @param string $name Name of the parameter being modified.
	 * @param string $value Value to set to the property with name $name.
	 * @param string $realValue Value built by the pseudo-method __set().
	 * @return boolean Returns true when it is alright to modify the property.
	 */
	protected function subSet($name, &$value, &$realValue) {
		//
		// Checking property rules.
		switch($name) {
			case "next_name":
				//
				// When the property being modified is the class
				// name, any search index on it is no longer
				// valid and it should be updated ASAP.
				$this->indexed = false;
				break;
			case "collection":
				//
				// When the property being modified is the
				// collection, last collector user has to be
				// updated.
				{
					global $wcUser;
					$this->collector = $wcUser->id;
				}
				$this->indexed = false;
				break;
		}
		//
		// There are no conditions in this method that my interrupt the
		// property change.
		return true;
	}
	//
	// Public class methods.
	/**
	 * This is a sorting method based on comic id and it can be used on usort
	 * and uasort functions.
	 *
	 * @param WCComic $a Left operand.
	 * @param WCComic $b Right operand.
	 * @return boolean Returns true when the id of $a is greater than $b's.
	 */
	public static function IdSort(WCComic &$a, WCComic &$b) {
		return $a->id() > $b->id();
	}
	/**
	 * This is a sorting method based on comic names and it can be used on
	 * usort and uasort functions.
	 *
	 * @param WCComic $a Left operand.
	 * @param WCComic $b Right operand.
	 * @return boolean Returns true when the name of $a is greater than
	 * $b's. Since it uses method 'name()' it considers cmc_name and
	 * cmc_next_name as well.
	 */
	public static function NameSort(WCComic &$a, WCComic &$b) {
		return $a->name() > $b->name();
	}
	/**
	 * This is the general sorting method and it can be used on usort and
	 * uasort functions.
	 *
	 * @param WCComic $a Left operand.
	 * @param WCComic $b Right operand.
	 * @return boolean Returns true when $a goes after $b.
	 */
	public static function Sort(WCComic &$a, WCComic &$b) {
		return self::NameSort($a, $b);
	}
}
